
#include "Lighter.h"
#include <assert.h>


unsigned int SearchCharPosInBlockArray (const Block* array, unsigned int count, const CharPos* pos, bool* inblock)
{
    assert (array);
    assert (pos);
    assert (inblock);

    *inblock = false;
    if (count == 0) {
        return 0;
    }

    unsigned int l = 0;
    unsigned int h = count-1;

    int ret;
    ret = CompareCharPosWithBlock (pos, &(array[0]));
    if (ret < 0) {
        return 0;
    }
    if (ret == 0) {
        *inblock = true;
        return 0;
    }

    ret = CompareCharPosWithBlock (pos, &(array[count-1]));
    if (ret > 0) {
        return count;
    }
    if (ret == 0) {
        *inblock = true;
        return count-1;
    }

    while (l != h) {
        if (h - l == 1) {
            // returns the closet bigger.
            return h;
        }
        unsigned int m = (l+h + 1)/2; // returns the closet bigger.
        ret = CompareCharPosWithBlock (pos, &(array[m]));
        if (ret  == 0) {
            *inblock = true;
            return m;
        }
        if (ret < 0 ) {
            h = m;
        } else if (ret > 0) {
            l = m;
        }
    }
    return l;
}

int CompareCharPosWithBlock(const CharPos* charpos, const Block* blk)
{
    assert (charpos);
    assert (blk);

    if (charpos->row < blk->row1) {
        return -1;
    }
    if ((charpos->row == blk->row1) && (charpos->idx < blk->idx1)) {
        return -1;
    }
       
    if ((charpos->row == blk->row2) && (charpos->idx > blk->idx2)) {
        return 1;
    }
    if (charpos->row > blk->row2) {
        return 1;
    }

    return 0;
}

CSyntaxLighter::CSyntaxLighter() : m_vecBlocks(BLOCK_INIT)
{
    m_nBlocks = 0;
    m_colors.string = DEFAULT_STRINGCOLOR;
    m_colors.comment = DEFAULT_COMMENTCOLOR;
    m_colors.normal = DEFAULT_NORMALCOLOR;
    m_colors.keyword = DEFAULT_KEYWORDCOLOR;
}

CSyntaxLighter::~CSyntaxLighter()
{
}
    
void CSyntaxLighter::SetDocument (const CTextDoc* doc)
{
    m_doc = (CTextDoc*)doc;
    ClearBlocks();
}

void CSyntaxLighter::SetSyntax (const CSyntaxConfig* syntax)
{
    m_syntax = (CSyntaxConfig*)syntax;
}

Block* CSyntaxLighter::GetBlocks()
{
    return m_vecBlocks.GetArrayPtr();
}

#define NO_QUOTE(syn)  (!syn->m_Quote || !syn->m_Quote[0])
#define NO_BLOCK_COMMENT(syn)  \
    (!syn->m_BlockCommentOn || !syn->m_BlockCommentOff || \
     syn->m_BlockCommentOn[0] == '\0' || syn->m_BlockCommentOff[0] == '\0')
#define NO_LINE_COMMENT(syn) (!syn->m_LineComment || syn->m_LineComment[0] == '\0')

int CSyntaxLighter::FindAllBlocks()
{
    assert (m_doc);
    
    ClearBlocks();
    if (!m_syntax || (NO_QUOTE(m_syntax) && NO_BLOCK_COMMENT(m_syntax) && NO_LINE_COMMENT(m_syntax))) {
        /* syntax must be set, otherwise no high ligthing */
        return 0;
    }

    CharPos pos = {0,0};
    unsigned int state = NORMAL;
    Block blk;

    while (pos.row < m_doc->m_nValidLines) {
        switch (state) {
        case NORMAL:
            blk.type = UNSPEC_BLOCK;
            blk.quote = '\0';
            state = FindNextBlockStart(&pos, &blk);
            break;
        case IN_BLOCK_COMMENT:
            assert (blk.type == COMMENT_BLOCK);
            state = FindNextBlockCommentOff (&pos, &blk);
            break;
        case IN_STRING:
            assert (blk.type == STRING_BLOCK);
            state = MatchQuote(&pos, &blk);
            break;
        default:
            assert(0);
            break;
        }
    }
    
    if (state != NORMAL) {
        blk.row2 = LAST_LINE;
        blk.idx2 = EOL;
        return AddToBlockVector(&blk);
    }
    return NORMAL;
}

int CSyntaxLighter::ColorTheLine(unsigned int line, const char* txt, COLORREF** color)
{
    assert (txt);
    assert (color);

    *color = NULL;
    if (!m_syntax) {
        return 0;
    }

    size_t size = strlen(txt);
    if (size == 0) {
        return 0;
    }

    *color = (COLORREF*) malloc (size * sizeof(COLORREF));
    if (!(*color)) {
        return 0;
    }

    bool inblock;
    CharPos pos;
    pos.row = line; pos.idx = 0;

    while (pos.idx < size) {
        unsigned int blkidx = SearchCharPosInBlockArray(m_vecBlocks.GetArrayPtr(), m_nBlocks, &pos, &inblock);
        if (!inblock) {
            if (blkidx < m_nBlocks && m_vecBlocks[blkidx].row1 == line) {
                ColorUntilNextBlock(*color, size, &(m_vecBlocks[blkidx]), &pos, txt);
            } else {
                ColorUntilNextBlock(*color, size, NULL, &pos, txt);
            }
        } else {
            ColorThisBlock(*color, size, &(m_vecBlocks[blkidx]), &pos);
        }
    }
    return size;
}

bool CSyntaxLighter::IsIndent(const char* str)
{
    if (!m_syntax) {
        return false;
    }
    return (m_syntax->m_Indent.FindToken(str));
}

bool CSyntaxLighter::IsUnindent(const char* str)
{
    if (!m_syntax) {
        return false;
    }
    return (m_syntax->m_Unindent.FindToken(str));
}

void CSyntaxLighter::ClearBlocks()
{
    m_nBlocks = 0;
    m_vecBlocks.Resize(BLOCK_INIT);
}

int CSyntaxLighter::AddBlock(const Block* blk)
{
    unsigned int size = m_vecBlocks.GetCount();
    if (size < m_nBlocks+1) {
        int ret = m_vecBlocks.Resize(size+BLOCK_GROW);
        if (ret < 0) {
            return ret;
        }
    }

    Block* arr = m_vecBlocks.GetArrayPtr();
    arr[m_nBlocks++] = *blk;
    return 0;
}

#define IS_SMALLEST_NON_NULL(a,b,c) (a && (!b || a<=b) && (!c || a <= c))

#define NEXT_LINE(pos) \
                                do { \
                                    pos->row ++; \
                                    pos->idx = 0; \
                                }while (0)

unsigned int CSyntaxLighter::FindNextBlockStart(CharPos* pos, Block* blk)
{
    assert (pos);
    assert (blk);
    assert (m_syntax);
    assert (pos->row < m_doc->m_nValidLines);


    char* line_comment = NULL;
    char* block_comment = NULL;
    const char* quote = NULL;

    char* txt0 = m_doc->GetLineText(pos->row);
    assert (pos->idx < strlen(txt0) || (pos->idx == 0));
    char* txt = txt0 + pos->idx;
    if (*txt0 == '\0') {
        NEXT_LINE(pos);
        return NORMAL;
    }

    if (!NO_BLOCK_COMMENT(m_syntax)) {
        block_comment = strstr (txt, m_syntax->m_BlockCommentOn);
    }
    if (m_syntax->m_LineComment) {
        line_comment = strstr (txt, m_syntax->m_LineComment);
    }
    if (m_syntax->m_Quote && m_syntax->m_Quote[0]) {
        quote = FindQuote (txt);
    }
    
    if (!block_comment && ! line_comment && !quote) {
        NEXT_LINE(pos);
        return NORMAL;
    }
    if (IS_SMALLEST_NON_NULL(quote, block_comment, line_comment)) {
        blk->row1 = pos->row;
        blk->idx1 = quote - txt0;
        blk->quote = *quote;
        blk->type = STRING_BLOCK;
        pos->idx = blk->idx1 + 1;
        if (txt0[pos->idx] == '\0') {
            NEXT_LINE(pos);
        }
        return IN_STRING;
    }
    if (IS_SMALLEST_NON_NULL(block_comment, quote, line_comment)) {
        blk->row1 = pos->row;
        blk->idx1 = block_comment - txt0;
        blk->type = COMMENT_BLOCK;
        pos->idx = blk->idx1 + strlen(m_syntax->m_BlockCommentOn);
        if (txt0[pos->idx] == '\0') {
            NEXT_LINE(pos);
        }
        return IN_BLOCK_COMMENT;
    }

    assert (IS_SMALLEST_NON_NULL(line_comment, block_comment, quote));
    
    blk->row1 = blk->row2 = pos->row;
    blk->idx1 = line_comment - txt0;
    blk->idx2 = EOL;
    blk->type = COMMENT_BLOCK;
    NEXT_LINE(pos);
    int ret = AddBlock(blk);
    if (ret < 0) {
        return ret;
    }
    return NORMAL;
}

#define IS_QUOTE(x,q) ((q != '\0' && x == q) || (q == 0 && strchr(m_syntax->m_Quote,x)))
#define IS_ESCAPE(x) (x != '\0' && x == m_syntax->m_Escape)
#define FOUND_QUOTE(c,c_prev,q) (!IS_ESCAPE(c_prev) && IS_QUOTE(c, q))

const char* CSyntaxLighter::FindQuote(const char* str, char quote/*='\0'*/)
{
    assert (m_syntax);
    assert (!NO_QUOTE(m_syntax));
    assert (str);

    const char* q_str = NULL;

    int c_prev, c;
    int idx = 0;

    c_prev = '\0';
    c = str[idx];
    while (c != '\0' && !FOUND_QUOTE(c, c_prev, quote) ) {
        c_prev = c;
        idx++;
        c = str[idx];
    }

    if (c != '\0') {
        q_str = str+idx;
    }
    return q_str;
}

unsigned int CSyntaxLighter::FindNextBlockCommentOff(CharPos* pos, Block* blk)
{
    assert (pos);
    assert (blk);
    assert (blk->type == COMMENT_BLOCK);
    assert (m_syntax);
    assert (pos->row < m_doc->m_nValidLines);
    assert (!NO_BLOCK_COMMENT(m_syntax));

    char* txt0 = m_doc->GetLineText(pos->row);
    assert (pos->idx < strlen(txt0) || (pos->idx == 0));
    char* txt = txt0 + pos->idx;
    if (*txt0 == '\0') {
        NEXT_LINE(pos);
        return IN_BLOCK_COMMENT;
    }

    const char* found = strstr (txt, m_syntax->m_BlockCommentOff);
    if (!found) {
        NEXT_LINE(pos);
        return IN_BLOCK_COMMENT;
    }

    pos->idx = (found - txt0) + strlen(m_syntax->m_BlockCommentOff);
    SetBlockEndPosition (blk, txt0, pos);
    return AddToBlockVector(blk);
}

unsigned int CSyntaxLighter::MatchQuote(CharPos* pos, Block* blk)
{
    assert (pos);
    assert (blk);
    assert (blk->type == STRING_BLOCK);
    assert (m_syntax);
    assert (pos->row < m_doc->m_nValidLines);
    assert (!NO_QUOTE(m_syntax));
    assert (blk->quote);

    char* txt0 = m_doc->GetLineText(pos->row);
    assert (pos->idx < strlen(txt0) || (pos->idx == 0));
    char* txt = txt0 + pos->idx;
    if (*txt0 == '\0') {
        NEXT_LINE(pos);
        return IN_STRING;
    }

    const char* found = FindQuote (txt, blk->quote);
    if (!found) {
        NEXT_LINE(pos);
        return IN_STRING;
    }

    pos->idx = (found - txt0) + 1;
    SetBlockEndPosition (blk, txt0, pos);
    return AddToBlockVector(blk);
}

void CSyntaxLighter::SetBlockEndPosition (Block* blk, const char* txt, CharPos* pos)
{
    blk->row2 = pos->row;
    blk->idx2 = pos->idx -1;
    if (txt[pos->idx] == '\0') {
        NEXT_LINE(pos);
    }
}

unsigned int CSyntaxLighter::AddToBlockVector (const Block* blk)
{
    int ret = AddBlock(blk);
    if (ret < 0) {
        return ret;
    }
    return NORMAL;
}

#define MAX_KEYWORD_LEN  256
#define MIN(a,b) (a < b ? a: b)

void CSyntaxLighter::ColorUntilNextBlock (COLORREF* colorarray, size_t size, const Block* nextblk, CharPos* pos, const char* txt)
{
    assert (colorarray);
    assert (pos);
    assert (m_syntax);

    unsigned int wordstart = EOL;

    while ((nextblk) && ((pos->row < nextblk->row1) && (pos->idx < size) ||
           (pos->row == nextblk->row1) && (pos->idx < nextblk->idx1)) || (!nextblk) && (pos->idx < size)) {
        if (strchr(m_syntax->m_Delimiter, txt[pos->idx])) {
            if (wordstart != EOL) {
                ColorWord(colorarray, txt, wordstart, pos->idx); 
            }
            colorarray[pos->idx] = m_colors.normal;
            wordstart = EOL; 
        } else {
            if (wordstart == EOL) {
                wordstart = pos->idx;
            }
        }
        (pos->idx)++;        
    }
    if (wordstart != EOL) {
        /* In this case, like: 
         *  keyword<end of line>,
         *  keyword<begin of block>
         */
        ColorWord(colorarray, txt, wordstart, pos->idx); 
    }
}

void CSyntaxLighter::ColorWord(COLORREF* colorarray, const char* txt, unsigned int start, unsigned int end)
{
    assert (txt);
    assert (colorarray);
    assert (start < end);
    assert (m_syntax);

    char word[MAX_KEYWORD_LEN];
    size_t cpysize = MIN(MAX_KEYWORD_LEN-1, (end - start));
    strncpy (word, txt+start, cpysize);
    word[cpysize] = '\0';
    COLORREF clr;
    if (m_syntax->m_Keywords.FindToken(word)) {
        clr = m_colors.keyword;
    } else {
        clr = m_colors.normal;
    }
    for (unsigned int i = start; i < end; i++) {
        colorarray[i] = clr;
    }
}

void CSyntaxLighter::ColorThisBlock(COLORREF* colorarray, size_t size, const Block* blk, CharPos* pos)
{
    assert (blk);
    assert (pos);
    assert (colorarray);
    assert (m_syntax);

    while ( (pos->row < blk->row2) && (pos->idx < size) ||
            (pos->row == blk->row2) && (pos->idx <= MIN(blk->idx2, size-1))) {
        colorarray[pos->idx] = (blk->type == COMMENT_BLOCK ? m_colors.comment : m_colors.string);
        (pos->idx)++;
    }
}
